﻿using System;
using Gadgeteer.Interfaces;
using Microsoft.SPOT;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.Interfaces;

namespace Gadgeteer.Modules.Sytech
{
    /// <summary>
    /// A Button Gadgeteer module.
    /// </summary>
    /// <example>
    /// <para>The following example show intialization of a <see cref="Button"/> object and the delegate that handles the 
    /// <see cref="Button.ButtonPressed"/> event.</para>
    /// <code>
    /// using System;
    /// using Microsoft.SPOT;
    /// using Microsoft.SPOT.Presentation;
    /// using Microsoft.SPOT.Presentation.Controls;
    /// using Microsoft.SPOT.Presentation.Media;
    ///
    /// using GT = Gadgeteer;
    /// using GTM = Gadgeteer.Modules;
    ///
    /// using Gadgeteer.Modules.Sytech;
    ///
    /// namespace GadgeteerAppGettingStarted
    /// {
    ///     public partial class Program
    ///     {
    ///         // This template uses the NANAO mainboard from Sytech Designs
    ///
    ///         // Define and initialize GTM.Modules here, specifying their socket numbers.        
    ///         GTM.Sytech.UsbDevice usbClient = new UsbDevice(1);
    ///         GTM.Sytech.Button button = new Button(2);
    ///
    ///         void ProgramStarted()
    ///         {
    ///             // Initialize event handlers here.
    ///             button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);
    ///
    ///             // Do one-time tasks here
    ///             Debug.Print("Program Started");
    ///         }
    ///
    ///         void button_ButtonPressed(Button sender, Button.ButtonState state)
    ///         {
    ///             Debug.Print("Button pressed.");
    ///         }
    ///     }
    /// }
    ///
    /// </code>
    /// </example>
    public class Button : GTM.Module
    {
        // This example implements  a driver in managed code for a simple Gadgeteer module.  The module uses a 
        // single GTI.InterruptInput to interact with a sensor that can be in either of two states: low or high.
        // The example code shows the recommended code pattern for exposing the property (IsHigh). 
        // The example also uses the recommended code pattern for exposing two events: ButtonHigh, ButtonLow. 
        // The triple-slash "///" comments shown will be used in the build process to create an XML file named
        // GTM.Sytech.Button. This file will provide Intellisense and documention for the
        // interface and make it easier for developers to use the Button module.        

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public Button(int socketNumber)
        {
            // This finds the Socket instance from the user-specified socket number.  
            // This will generate user-friendly error messages if the socket is invalid.
            // If there is more than one socket on this module, then instead of "null" for the last parameter, 
            // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);

            // validate the socket
            socket.EnsureTypeIsSupported(new char[] { 'X', 'Y' }, this);
 

            // This creates an GTI.InterruptInput interface. The interfaces under the GTI namespace provide easy ways to build common modules.
            // This also generates user-friendly error messages automatically, e.g. if the user chooses a socket incompatible with an interrupt input.
            this.input = new GTI.InterruptInput(socket, GT.Socket.Pin.Three, GTI.GlitchFilterMode.On, GTI.ResistorMode.PullUp, GTI.InterruptMode.RisingAndFallingEdge, this);

            // This registers a handler for the interrupt event of the interrupt input (which is below)
            this.input.Interrupt += new GTI.InterruptInput.InterruptEventHandler(this._input_Interrupt);

            //Create the LED output
            led = new DigitalOutput(socket,GT.Socket.Pin.Four,false,this);

            // intialise with the LED off
            LEDMode = LEDModes.Off;
        }

        private void _input_Interrupt(GTI.InterruptInput input, bool value)
        {
            ButtonState buttonState = value ? ButtonState.Released : ButtonState.Pressed;
            //Handle if button pressed or released

            switch (buttonState)
            {
                case ButtonState.Released:
                    if (LEDMode == LEDModes.OnWhilePressed)
                        TurnLEDOff();
                    else if (LEDMode == LEDModes.OnWhileReleased)
                        TurnLEDOn();
                    else if (LEDMode == LEDModes.ToggleWhenReleased)
                        ToggleLED();
                    break;
                case ButtonState.Pressed:
                    if (LEDMode == LEDModes.OnWhilePressed)
                        TurnLEDOn();
                    else if (LEDMode == LEDModes.OnWhileReleased)
                        TurnLEDOff();
                    else if (LEDMode == LEDModes.ToggleWhenPressed)
                        ToggleLED();
                    break;
            }

            this.OnButtonEvent(this, buttonState);
        }

        /// <summary>
        /// Gets a value that indicates whether the state of this Button is Pressed.
        /// </summary>
        public bool IsPressed
        {
            get
            {
                return !this.input.Read();
            }
        }

        private GTI.InterruptInput input;
        private GTI.DigitalOutput led;

        #region LED Handler

        private LEDModes _ledMode;

        /// <summary>
        /// Gets or sets the LED's current mode of operation.
        /// </summary>
        public LEDModes LEDMode
        {
            get
            {
                return _ledMode;
            }
            set
            {
                _ledMode = value;

                if (_ledMode == LEDModes.On || _ledMode == LEDModes.OnWhilePressed && IsPressed || _ledMode == LEDModes.OnWhileReleased && !IsPressed)
                    TurnLEDOn();
                else if (_ledMode == LEDModes.Off || _ledMode == LEDModes.OnWhileReleased && IsPressed || _ledMode == LEDModes.OnWhilePressed && !IsPressed)
                    TurnLEDOff();
            }

        }

        /// <summary>
        /// Turns on the module's LED.
        /// </summary>
        public void TurnLEDOn()
        {
            led.Write(true);
        }

        /// <summary>
        /// Turns off the module's LED.
        /// </summary>
        public void TurnLEDOff()
        {
            led.Write(false);
        }

        /// <summary>
        /// Toggles the module's LED. If the LED is currently on, it is turned off. If it is currently off, it is turned on.
        /// </summary>
        public void ToggleLED()
        {
            if (IsLedOn)
                TurnLEDOff();
            else
                TurnLEDOn();
        }

        /// <summary>
        /// Gets a boolean value that indicates whether the module's LED is currently lit (true = lit, false = off).
        /// </summary>
        public bool IsLedOn
        {
            get
            {
                return led.Read();
            }
        }
        #endregion

      

        /// <summary>
        /// Represents the delegate that is used to handle the <see cref="ButtonReleased"/>
        /// and <see cref="ButtonPressed"/> events.
        /// </summary>
        /// <param name="sender">The <see cref="Button"/> object that raised the event.</param>
        /// <param name="state">The state of the Button</param>
        public delegate void ButtonEventHandler(Button sender, ButtonState state);

        /// <summary>
        /// Raised when the state of <see cref="Button"/> is high.
        /// </summary>
        /// <remarks>
        /// Implement this event handler and the <see cref="ButtonPressed"/> event handler
        /// when you want to provide an action associated with Button activity.
        /// The state of the Button is passed to the <see cref="ButtonEventHandler"/> delegate,
        /// so you can use the same event handler for both Button states.
        /// </remarks>
        public event ButtonEventHandler ButtonReleased;

        /// <summary>
        /// Raised when the state of <see cref="Button"/> is low.
        /// </summary>
        /// <remarks>
        /// Implement this event handler and the <see cref="ButtonReleased"/> event handler
        /// when you want to provide an action associated with Button activity.
        /// Since the state of the Button is passed to the <see cref="ButtonEventHandler"/> delegate,
        /// you can use the same event handler for both Button states.
        /// </remarks>
        public event ButtonEventHandler ButtonPressed;

        private ButtonEventHandler onButton;

        /// <summary>
        /// Raises the <see cref="ButtonPressed"/> or <see cref="ButtonReleased"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="Button"/> that raised the event.</param>
        /// <param name="ButtonState">The state of the Button.</param>
        protected virtual void OnButtonEvent(Button sender, ButtonState ButtonState)
        {
            if (this.onButton == null)
            {
                this.onButton = new ButtonEventHandler(this.OnButtonEvent);
            }

            if (Program.CheckAndInvoke((ButtonState == ButtonState.Released ? this.ButtonReleased : this.ButtonPressed), this.onButton, sender, ButtonState))
            {
                switch (ButtonState)
                {
                    case ButtonState.Released:
                        this.ButtonReleased(sender, ButtonState);
                        break;
                    case ButtonState.Pressed:
                        this.ButtonPressed(sender, ButtonState);
                        break;
                }
            }
        }

        #region enums

        /// <summary>
        /// Represents the state of the <see cref="Button"/> object.
        /// </summary>
        public enum ButtonState
        {
            /// <summary>
            /// The state of Button is low - pressed.
            /// </summary>
            Pressed = 0,
            /// <summary>
            /// The state of Button is high - released.
            /// </summary>
            Released = 1
        }


        /// <summary>
        /// Enuerates the various modes a LED can be set to.
        /// </summary>
        public enum LEDModes
        {
            /// <summary>
            /// The LED is on regardless of the button state.
            /// </summary>
            On,
            /// <summary>
            /// The LED is off regardless of the button state.
            /// </summary>
            Off,
            /// <summary>
            /// The LED changes state whenever the button is pressed.
            /// </summary>
            ToggleWhenPressed,
            /// <summary>
            /// The LED changes state whenever the button is released.
            /// </summary>
            ToggleWhenReleased,
            /// <summary>
            ///  The LED is on while the button is pressed.
            /// </summary>
            OnWhilePressed,
            /// <summary>
            /// The LED is on except when the button is pressed.
            /// </summary>
            OnWhileReleased
        }


        #endregion
    }
}
